package org.ricks.net.http.restful.handler;

import org.ricks.net.json.JSONArray;
import org.ricks.net.json.JSONObject;
import org.ricks.net.http.enums.HeaderNameEnum;
import org.ricks.net.http.enums.HeaderValueEnum;
import org.ricks.net.http.restful.annotation.Param;
import org.ricks.net.http.restful.intercept.MethodInterceptor;
import org.ricks.net.http.restful.intercept.MethodInvocation;
import org.ricks.net.http.restful.intercept.MethodInvocationImpl;
import org.ricks.net.http.server.HttpRequest;
import org.ricks.net.http.server.HttpResponse;
import org.ricks.net.http.server.HttpServerHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.Map;

class ControllerHandler extends HttpServerHandler {
    private final Method method;
    private final Object controller;
    private final MethodInterceptor interceptor;
    private final ParamInvoker[] paramInvokers;
    private boolean needContext;

    public ControllerHandler(Method method,Object controller, MethodInterceptor interceptor) {
        this.method = method;
        this.controller = controller;
        this.interceptor = interceptor;
        Parameter[] parameters = method.getParameters();
        paramInvokers = new ParamInvoker[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Class type = parameter.getType();

            if (type == HttpRequest.class) {
                paramInvokers[i] = ParamInvoker.HttpRequestHttpRequest;
                continue;
            }
            if (type == HttpResponse.class) {
                paramInvokers[i] = ParamInvoker.HttpResponseHttpRequest;
                continue;
            }
            needContext = true;

            Param param = parameter.getAnnotation(Param.class);
            if (param != null) {
                if(int.class.isAssignableFrom(type) || Integer.class.isAssignableFrom(type)) {
                    paramInvokers[i] = (request, response, context) ->  !context.getJsonObject().has(param.value()) ? 0 : context.getJsonObject().getInt(param.value());
                    continue;
                } else if(long.class.isAssignableFrom(type) || Long.class.isAssignableFrom(type)) {
                    paramInvokers[i] = (request, response, context) -> !context.getJsonObject().has(param.value()) ? 0 : context.getJsonObject().getLong(param.value());
                    continue;
                } else if(boolean.class.isAssignableFrom(type) || Boolean.class.isAssignableFrom(type)) {
                    paramInvokers[i] = (request, response, context) -> !context.getJsonObject().has(param.value()) ? false : context.getJsonObject().getBoolean(param.value());
                    continue;
                } else if(float.class.isAssignableFrom(type) || Float.class.isAssignableFrom(type)) {
                    paramInvokers[i] = (request, response, context) -> !context.getJsonObject().has(param.value()) ? 0 : context.getJsonObject().getFloat(param.value());
                    continue;
                } else if(double.class.isAssignableFrom(type) || Double.class.isAssignableFrom(type)) {
                    paramInvokers[i] = (request, response, context) -> !context.getJsonObject().has(param.value()) ? 0 : context.getJsonObject().getDouble(param.value());
                    continue;
                }  else if(short.class.isAssignableFrom(type) || Short.class.isAssignableFrom(type)) {
                    paramInvokers[i] = (request, response, context) -> !context.getJsonObject().has(param.value()) ? 0 : context.getJsonObject().getShort(param.value());
                    continue;
                }  else if(byte.class.isAssignableFrom(type) || Byte.class.isAssignableFrom(type)) {
                    paramInvokers[i] = (request, response, context) -> !context.getJsonObject().has(param.value()) ? 0 : context.getJsonObject().getByte(param.value());
                    continue;
                }  else if(byte[].class.isAssignableFrom(type) || Byte[].class.isAssignableFrom(type)) {
//                    paramInvokers[i] = (request, response, context) -> {
//                        if (!context.getJsonObject().has(param.value())) {
//                            return new byte[]{};
//                        }
//                        String base64String = context.getJsonObject().getString(param.value());
//                        return java.util.Base64.getDecoder().decode(base64String);
//                    };
                    paramInvokers[i] = (request, response, context) -> {
                        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
                        request.getInputStream().transferTo(buffer);
                        return buffer.toByteArray();
                    };
                    continue;
                } else if(String.class.isAssignableFrom(type)) {
                    paramInvokers[i] = (request, response, context) -> !context.getJsonObject().has(param.value()) ? "" : context.getJsonObject().getString(param.value());
                    continue;
                }  else if(JSONObject.class.isAssignableFrom(type)) {
                    paramInvokers[i] = (request, response, context) -> !context.getJsonObject().has(param.value()) ? "" : context.getJsonObject().getJSONObject(param.value());
                    continue;
                } else if(JSONArray.class.isAssignableFrom(type)) {
                    paramInvokers[i] = (request, response, context) -> !context.getJsonObject().has(param.value()) ? "" : context.getJsonObject().getJSONArray(param.value());
                    continue;
                }
                throw new IllegalArgumentException("http arg 未知参数类型[" +type + "]");
            }

            //param为null，说明是对象转换
            if (Collection.class.isAssignableFrom(parameter.getType())) {
                paramInvokers[i] = (request, response, context) -> context.getJsonArray().toList();
            } else if(Map.class.isAssignableFrom(parameter.getType())) {
                paramInvokers[i] = (request, response, context) -> context.getJsonObject().toMap();
            }
            paramInvokers[i] = (request, response, context) -> context.getJsonObject();
        }
    }

    @Override
    public void handle(HttpRequest request, HttpResponse response) throws Throwable {
        Object[] params = getParams(request, response);
        method.setAccessible(true);
        MethodInvocation invocation = new MethodInvocationImpl(method, params, controller, request, response);
        Object rsp = interceptor.invoke(invocation);
//            Object rsp = method.invoke(controller, params);
        sendResponse(response,rsp);
    }


    private void sendResponse(HttpResponse response, Object rsp) throws IOException {
        if (rsp != null) {
            byte[] bytes;
            switch (rsp) {
                case String s -> bytes = s.getBytes();
//                case MsgException msgException -> {
//                    response.setHeader(HeaderNameEnum.CONTENT_TYPE.getName(), HeaderValueEnum.APPLICATION_JSON.getName());
//                    JSONObject jsonObject = new JSONObject(msgException);
//                    JSONObject converJsonObject = new JSONObject(jsonObject, "msgCode", "message");
//                    bytes = converJsonObject.toString().getBytes();
//                }
                case JSONObject jsonObject -> {
                    response.setHeader(HeaderNameEnum.CONTENT_TYPE.getName(), HeaderValueEnum.APPLICATION_JSON.getName());
                    bytes = jsonObject.toString().getBytes();
                }
                default -> {
                    response.setHeader(HeaderNameEnum.CONTENT_TYPE.getName(), HeaderValueEnum.APPLICATION_JSON.getName());
                    JSONObject jsonObject = new JSONObject(rsp);
                    bytes = jsonObject.toString().getBytes();
                }
            }
            //如果在controller中已经触发过write，此处的contentLength将不准，且不会生效
            response.setContentLength(bytes.length);
            response.write(bytes);
        }
    }

    private Object[] getParams(HttpRequest request, HttpResponse response) throws IOException {
        Object[] params = new Object[paramInvokers.length];

        InvokerContext context = null;
        if (needContext) {
            context = new InvokerContext();
            Object object;
            if (request.getContentType() != null && request.getContentType().startsWith("application/json")) {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] bytes = new byte[1024];
                int len = 0;
                InputStream inputStream = request.getInputStream();
                while ((len = inputStream.read(bytes)) != -1) {
                    out.write(bytes, 0, len);
                }
//                object = JSONWriter.valueToString(out.toByteArray());
                String str = new String(out.toByteArray()).trim();
                if(str.startsWith("{")) {
                    object = new JSONObject(str);
                } else if(str.startsWith("[")) {
                    object = new JSONArray(str);
                } else {
                    throw new RuntimeException("非法请求参数 ContentType[application/json] - "+ str +"无法正常解析 ！");
                }
            } else {
                JSONObject jsonObject = new JSONObject();
                request.getParameters().keySet().forEach(param -> {
                    jsonObject.put(param, request.getParameter(param));
                });
                object = jsonObject;
            }

            context.setJsonObject(object);
        }
        for (int i = 0; i < params.length; i++) {
            params[i] = paramInvokers[i].invoker(request, response, context);
        }
        return params;
    }

    public interface ParamInvoker {
        ParamInvoker HttpRequestHttpRequest = (request, response, context) -> request;

        ParamInvoker HttpResponseHttpRequest = (request, response, context) -> response;

        Object invoker(HttpRequest request, HttpResponse response, InvokerContext context) throws IOException;

    }

    public static class InvokerContext {
        private Object jsonObject;

        public JSONObject getJsonObject() {
            return (JSONObject) jsonObject;
        }

        public JSONArray getJsonArray() {
            return (JSONArray) jsonObject;
        }

        public void setJsonObject(Object jsonObject) {
            this.jsonObject = jsonObject;
        }

    }
}
